/*
* Copyright (C) 2000 Arne Schirmacher <arne@schirmacher.de>
* Copyright (C) 2001-2007 Dan Dennedy <dan@dennedy.org>
* Copyright (C) 2007 Stéphane Brunner <stephane.brunner@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "displayer.h"

#include <QX11Info>


AspectRatioCalculator::AspectRatioCalculator( int widgetWidth, int widgetHeight,
        int imageWidth, int imageHeight,
        int maxImageWidth, int maxImageHeight )
{
	double ratioWidth = ( double ) widgetWidth / ( double ) imageWidth;
	double ratioHeight = ( double ) widgetHeight / ( double ) imageHeight;
	double ratioConstant = ratioHeight < ratioWidth ?
	                       ratioHeight : ratioWidth;
	width = ( int ) ( imageWidth * ratioConstant + 0.5 );
	height = ( int ) ( imageHeight * ratioConstant + 0.5 );
	if ( width > maxImageWidth )
		width = maxImageWidth;
	if ( height > maxImageHeight )
		height = maxImageHeight;
	x = ( widgetWidth - width ) / 2;
	y = ( widgetHeight - height ) / 2;
}

AspectRatioCalculator::AspectRatioCalculator( int widgetWidth, int widgetHeight,
        int imageWidth, int imageHeight, bool isPAL, bool isWidescreen )
{
	if ( isWidescreen )
	{
		if ( isPAL )
		{
			// PAL: 720 x 576 at 16:9 aspect ratio: Use an image size of 1024 x 576.
			imageWidth = imageWidth * 1024 / 720;
			imageHeight = imageHeight * imageHeight / 576;
		}
		else
		{
			// NTSC: 720 x 480 at 16:9 aspect ratio: Use an image size of 854 x 480
			imageWidth = imageWidth * 854 / 720;
			imageHeight = imageHeight * imageHeight / 480;
		}
	}
	else
	{
		if ( isPAL )
		{
			// PAL: 720 x 576 at 4:3 aspect ratio: Use an image size of 768 x 576.
			imageWidth = imageWidth * 768 / 720;
			imageHeight = imageHeight * imageHeight / 576;
		}
		else
		{
			// NTSC: 720 x 480 at 4:3 aspect ratio: Use an image size of 720 x 540
			imageWidth = imageWidth * imageWidth / 720;
			imageHeight = imageHeight * 540 / 480;
		}
	}

	// cerr << "Changed : " << imageWidth << "x" << imageHeight << endl;

	double ratioWidth = ( double ) widgetWidth / ( double ) imageWidth;
	double ratioHeight = ( double ) widgetHeight / ( double ) imageHeight;

	if ( ratioHeight < ratioWidth )
	{
		width = ( int ) ( imageWidth * ratioHeight + 0.5 );
		height = ( int ) ( imageHeight * ratioHeight + 0.5 );
	}
	else
	{
		width = ( int ) ( imageWidth * ratioWidth + 0.5 );
		height = ( int ) ( imageHeight * ratioWidth + 0.5 );
	}

	// cerr << "Final   : " << width << "x" << height << endl;
	// cerr << "Ratio   : " << (double)width / (double)height << endl;

	x = ( widgetWidth - width ) / 2;
	y = ( widgetHeight - height ) / 2;
}

/** Indicates if an object can be used to render images on the running
	system.
*/

/** Indicates the format required by the abstract put method.
*/

DisplayerInput Displayer::format()
{
	return DISPLAY_NONE;
}

/** Expect width of input to put.
*/

int Displayer::preferredWidth()
{
	return img_width;
}

/** Expect width of input to put.
*/

int Displayer::preferredHeight()
{
	return img_height;
}

/** Put an image of a given width and height with the expected input
	format (as indicated by the format method).
 
	\param image	image of correct format and specified width/height
	\param width	width of image
	\param height	height of image
*/

void Displayer::put( AVPicture *image, int width, int height )
{
//	if ( width == preferredWidth() && height == preferredHeight() )
//	{
		put( image );
//	}
//	else
//	{
//		reformat( format(), format(), image, width, height );
//		put( pixels );
//	}
}

/** Put an image of a given width and height with the input format
	specified.
 
	\param format	format of image
	\param image	image of specified width/height
	\param width	width of image
	\param height	height of image
 
*/

void Displayer::put( DisplayerInput inFormat, AVPicture *image, int width, int height )
{
//	if ( format() == inFormat &&
//	        width == preferredWidth() && height == preferredHeight() )
//	{
		put( image );
//	}
//	else
//	{
//		reformat( inFormat, format(), image, width, height ); // 4.5ms
//		put( pixels );
//	}
}
//
//void Displayer::Reformat424( unsigned char *image, int inWidth, int inHeight, int outWidth, int outHeight )
//{
//	// Fixed value for rounding to the middle pixel (instead of defaulting to top left)
//	int rounding = 1 << 15;
//	// xfactor and yfactor represent the granularity of the output, relative to the input
//	unsigned int xfactor = ( inWidth << 16 ) / outWidth;
//	unsigned int yfactor = ( inHeight << 16 ) / outHeight;
//	// multiplication of x and yfactors to the output height
//	unsigned int ymax = yfactor * outHeight;
//	unsigned int xmax = xfactor * outWidth;
//	// y is used to point to the start of each line in the input
//	unsigned int y = 0;
//	// i is a temporary variable used in iterating through each line
//	unsigned int i = 0;
//	// o is the position in the output
//	unsigned int o = 0;
//
//	for ( unsigned int yft = 0; yft < ymax; yft += yfactor )
//	{
//		y = ( ( yft + rounding ) >> 16 ) * inWidth * 4;
//		for ( unsigned int xft = 0; xft < xmax; xft += xfactor )
//		{
//			i = y + ( ( xft + rounding ) >> 16 ) * 4;
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ];
//		}
//	}
//}
//
//void Displayer::Reformat222( unsigned char *image, int inWidth, int inHeight, int outWidth, int outHeight )
//{
//	// Fixed value for rounding to the middle pixel (instead of defaulting to top left)
//	int rounding = 1 << 15;
//	// xfactor and yfactor represent the granularity of the output, relative to the input
//	unsigned int xfactor = ( inWidth << 16 ) / outWidth;
//	unsigned int yfactor = ( inHeight << 16 ) / outHeight;
//	// multiplication of x and yfactors to the output height
//	unsigned int ymax = yfactor * outHeight;
//	unsigned int xmax = xfactor * outWidth;
//	// y is used to point to the start of each line in the input
//	unsigned int y = 0;
//	// i is a temporary variable used in iterating through each line
//	unsigned int i = 0;
//	// o is the position in the output
//	unsigned int o = 0;
//
//	for ( unsigned int yft = 0; yft < ymax; yft += yfactor )
//	{
//		y = ( ( yft + rounding ) >> 16 ) * inWidth * 2;
//		for ( unsigned int xft = 0; xft < xmax; xft += xfactor )
//		{
//			i = y + ( ( xft + rounding ) >> 16 ) * 2;
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ];
//		}
//	}
//}
//
//void Displayer::Reformat323( unsigned char *image, int inWidth, int inHeight, int outWidth, int outHeight )
//{
//	// Fixed value for rounding to the middle pixel (instead of defaulting to top left)
//	int rounding = 1 << 15;
//	// xfactor and yfactor represent the granularity of the output, relative to the input
//	unsigned int xfactor = ( inWidth << 16 ) / outWidth;
//	unsigned int yfactor = ( inHeight << 16 ) / outHeight;
//	// multiplication of x and yfactors to the output height
//	unsigned int ymax = yfactor * outHeight;
//	unsigned int xmax = xfactor * outWidth;
//	// y is used to point to the start of each line in the input
//	unsigned int y = 0;
//	// i is a temporary variable used in iterating through each line
//	unsigned int i = 0;
//	// o is the position in the output
//	unsigned int o = 0;
//
//	for ( unsigned int yft = 0; yft < ymax; yft += yfactor )
//	{
//		y = ( ( yft + rounding ) >> 16 ) * inWidth * 3;
//		for ( unsigned int xft = 0; xft < xmax; xft += xfactor )
//		{
//			i = y + ( ( xft + rounding ) >> 16 ) * 3;
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ];
//		}
//	}
//}
//
//void Displayer::Reformat323R( unsigned char *image, int inWidth, int inHeight, int outWidth, int outHeight )
//{
//	// Fixed value for rounding to the middle pixel (instead of defaulting to top left)
//	int rounding = 1 << 15;
//	// xfactor and yfactor represent the granularity of the output, relative to the input
//	unsigned int xfactor = ( inWidth << 16 ) / outWidth;
//	unsigned int yfactor = ( inHeight << 16 ) / outHeight;
//	// multiplication of x and yfactors to the output height
//	unsigned int ymax = yfactor * outHeight;
//	unsigned int xmax = xfactor * outWidth;
//	// y is used to point to the start of each line in the input
//	unsigned int y = 0;
//	// i is a temporary variable used in iterating through each line
//	unsigned int i = 0;
//	// o is the position in the output
//	unsigned int o = 0;
//
//	for ( unsigned int yft = 0; yft < ymax; yft += yfactor )
//	{
//		y = ( ( yft + rounding ) >> 16 ) * inWidth * 3;
//		for ( unsigned int xft = 0; xft < xmax; xft += xfactor )
//		{
//			i = y + ( ( xft + rounding ) >> 16 ) * 3 + 2;
//			pixels[ o ++ ] = image[ i ];
//			pixels[ o ++ ] = image[ -- i ];
//			pixels[ o ++ ] = image[ -- i ];
//		}
//	}
//}
//
//void Displayer::Reformat324( unsigned char *image, int inWidth, int inHeight, int outWidth, int outHeight )
//{
//	// Fixed value for rounding to the middle pixel (instead of defaulting to top left)
//	int rounding = 1 << 15;
//	// xfactor and yfactor represent the granularity of the output, relative to the input
//	unsigned int xfactor = ( inWidth << 16 ) / outWidth;
//	unsigned int yfactor = ( inHeight << 16 ) / outHeight;
//	// multiplication of x and yfactors to the output height
//	unsigned int ymax = yfactor * outHeight;
//	unsigned int xmax = xfactor * outWidth;
//	// y is used to point to the start of each line in the input
//	unsigned int y = 0;
//	// i is a temporary variable used in iterating through each line
//	unsigned int i = 0;
//	// o is the position in the output
//	unsigned int o = 0;
//
//	for ( unsigned int yft = 0; yft < ymax; yft += yfactor )
//	{
//		y = ( ( yft + rounding ) >> 16 ) * inWidth * 3;
//		for ( unsigned int xft = 0; xft < xmax; xft += xfactor )
//		{
//			i = y + ( ( xft + rounding ) >> 16 ) * 3;
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ++ ];
//			pixels[ o ++ ] = image[ i ];
//			pixels[ o ++ ] = 0;
//		}
//	}
//}
//
///** Reformat.
// 
//  	\param inFormat
//	\param outFromat
//	\param image
//	\param width
//	\param height
//*/
//
//void Displayer::reformat( DisplayerInput inFormat, int outFormat, void *image,
//                          int width, int height )
//{
//	//struct timeval tv;
//	//gettimeofday(&tv, NULL);
//	//long long starttime = tv.tv_sec * 1000000 + tv.tv_usec;
//	unsigned char * img = ( unsigned char * ) image;
//	if ( inFormat == DISPLAY_YUV && outFormat == DISPLAY_YUV ) {
//		Reformat424( img, width, height, this->preferredWidth(), this->preferredHeight() );
//	}
//	else if ( inFormat == DISPLAY_RGB16 && outFormat == DISPLAY_RGB16 ) {
//		Reformat222( img, width, height, this->preferredWidth(), this->preferredHeight() );
//	}
//	else if ( inFormat == DISPLAY_BGR && outFormat == DISPLAY_RGB ) {
//		Reformat323( img, width, height, this->preferredWidth(), this->preferredHeight() );
//	}
//	else if ( inFormat == DISPLAY_BGR && outFormat == DISPLAY_BGR0 ) {
//		Reformat324( img, width, height, this->preferredWidth(), this->preferredHeight() );
//	}
//	// Incorrect, but covers the most correctly [take care when introducing new formats]
//	else if ( inFormat == outFormat ) {
//		Reformat323( img, width, height, this->preferredWidth(), this->preferredHeight() );
//	}
//
//	//gettimeofday(&tv, NULL);
//	//long long endtime = tv.tv_sec * 1000000 + tv.tv_usec;
//	//cerr << "Reformatting took " << endtime - starttime << endl;
//}

/** XVideo Constructor. */
XvDisplayer::XvDisplayer( QWidget *drawingarea, int width, int height, bool isPAL, bool isWidescreen ) :
		xvImage( NULL )
{
	cerr << ">> Trying XVideo at " << width << "x" << height << endl;

	this->drawingarea = drawingarea;
	img_width = width;
	img_height = height;
	this->isPAL = isPAL;
	this->isWidescreen = isWidescreen;

//	shmInfo.shmaddr = NULL;
	gotPort = false;

	window = drawingarea->winId();
	display = QX11Info::display();

	unsigned int	count;

	struct timespec t = { 0, 10*1000*1000 }; // 10ms
	nanosleep( &t, NULL );

//	QApplication::lock();
	XvAdaptorInfo	*adaptorInfo;

	if ( XvQueryAdaptors( display, window, &count, &adaptorInfo ) == Success )
	{

		cerr << ">>> XvQueryAdaptors count: " << count << endl;
		for ( unsigned int n = 0; gotPort == false && n < count; ++n )
		{
			// Diagnostics
			cerr << ">>> Xv: " << adaptorInfo[ n ].name
			<< ": ports " << adaptorInfo[ n ].base_id
			<< " - " << adaptorInfo[ n ].base_id +
			adaptorInfo[ n ].num_ports - 1
			<< endl;

			for ( port = adaptorInfo[ n ].base_id;
			        port < adaptorInfo[ n ].base_id + adaptorInfo[ n ].num_ports;
			        port ++ )
			{
				if ( XvGrabPort( display, port, CurrentTime ) == 0 )
				{

					int formats;
					XvImageFormatValues *list;

					list = XvListImageFormats( display, port, &formats );

					cerr << ">>> formats supported: " << formats << endl;

					for ( int i = 0; i < formats; i ++ )
					{
						fprintf( stderr, ">>>     0x%x (%c%c%c%c) %s\n",
						         list[ i ].id,
						         ( list[ i ].id ) & 0xff,
						         ( list[ i ].id >> 8 ) & 0xff,
						         ( list[ i ].id >> 16 ) & 0xff,
						         ( list[ i ].id >> 24 ) & 0xff,
						         ( list[ i ].format == XvPacked ) ? "packed" : "planar" );
						if ( list[ i ].id == 0x32595559 && !gotPort )
							gotPort = true;
					}

					if ( !gotPort )
					{
						XvUngrabPort( display, port, CurrentTime );
					}
					else
					{
						grabbedPort = port;
						break;
					}
				}
			}
		}

		if ( gotPort )
		{
			int num;
			unsigned int unum;
			XvEncodingInfo *enc;
			
			XvQueryEncodings( display, grabbedPort, &unum, &enc );
			for ( unsigned int index = 0; index < unum; index ++ )
			{
				fprintf( stderr, ">>> %d: %s, %ldx%ld rate = %d/%d\n", index, enc->name,
				        enc->width, enc->height, enc->rate.numerator,
				        enc->rate.denominator );
			}
			
			XvAttribute *xvattr = XvQueryPortAttributes( display, port, &num );
			for ( int k = 0; k < num; k++ )
			{
				if ( xvattr[k].flags & XvSettable ) 
				{
					if ( strcmp( xvattr[k].name, "XV_AUTOPAINT_COLORKEY") == 0 )
					{
						Atom val_atom = XInternAtom( display, xvattr[k].name, False );
						if ( XvSetPortAttribute( display, port, val_atom, 1 ) != Success )
							fprintf(stderr, "Couldn't set Xv attribute %s\n", xvattr[k].name);
					}
					else if (  strcmp( xvattr[k].name, "XV_COLORKEY") == 0 )
					{
						Atom val_atom = XInternAtom( display, xvattr[k].name, False );
						if ( XvSetPortAttribute( display, port, val_atom, 0x010102 ) != Success )
							fprintf(stderr, "Couldn't set Xv attribute %s\n", xvattr[k].name);
					}
				}
			}
		}

		if ( gotPort )
		{
			gc = XCreateGC( display, window, 0, &values );

#if 0
			xvImage = ( XvImage * ) XvShmCreateImage( display, port, 0x32595559, 0, width, height, &shmInfo );

			shmInfo.shmid = shmget( IPC_PRIVATE, xvImage->data_size, IPC_CREAT | 0777 );
			if (shmInfo.shmid < 0) {
				perror("shmget");
				gotPort = false;
			} else {
				shmInfo.shmaddr = ( char * ) shmat( shmInfo.shmid, 0, 0 );
				xvImage->data = shmInfo.shmaddr;
				shmInfo.readOnly = 0;
				if ( !XShmAttach( display, &shmInfo ) )
				{
					gotPort = false;
				}
				XSync( display, false );
				shmctl( shmInfo.shmid, IPC_RMID, 0 );
			}
#endif
#if 1
				xvImage = ( XvImage * ) XvCreateImage( display, port, 0x32595559, pix, width , height );
#endif
		}
	}
	else
	{
		gotPort = false;
	}
//	QApplication::unlock();
}

XvDisplayer::~XvDisplayer()
{
	cerr << ">> Destroying XV Displayer" << endl;

//	QApplication::lock();
	if ( gotPort )
	{
		XvUngrabPort( display, grabbedPort, CurrentTime );
	}

	if ( xvImage != NULL )
		XvStopVideo( display, port, window );

#if 0
	if ( shmInfo.shmaddr != NULL )
	{
		XShmDetach( display, &shmInfo );
		shmctl( shmInfo.shmid, IPC_RMID, 0 );
		shmdt( shmInfo.shmaddr );
	}
#endif
	if ( xvImage != NULL )
		XFree( xvImage );

//	QApplication::unlock();
}

bool XvDisplayer::usable()
{
	return gotPort;
}

DisplayerInput XvDisplayer::format()
{
	return DISPLAY_YUV;
}

void XvDisplayer::put( AVPicture *image )
{
	if (xvImage != NULL) {
//		QApplication::lock();
		AspectRatioCalculator calc( drawingarea->width(), drawingarea->height(),
		                            this->preferredWidth(), this->preferredHeight(),
		                            isPAL, isWidescreen );
	
		memcpy( xvImage->data, image, xvImage->data_size );
	
	
#if 0
		XvShmPutImage( display, port, window, gc, xvImage,
		               0, 0, this->preferredWidth(), this->preferredHeight(),
		               calc.x, calc.y, calc.width, calc.height, false );
#endif

#if 1
		XvPutImage( display, port, window, gc, xvImage,
		            0, 0, this->preferredWidth(), this->preferredHeight(),
		            calc.x, calc.y, calc.width, calc.height );
#endif
//		QApplication::unlock();
	}
}

/** SDL Video Constructor. */
SdlDisplayer::SdlDisplayer( QWidget *drawingarea, int width, int height, bool isPAL, bool isWidescreen )
{
	cerr << ">> Trying SDL Video at " << width << "x" << height << endl;

	this->drawingarea = drawingarea;
	img_width = width;
	img_height = height;
	this->isPAL = isPAL;
	this->isWidescreen = isWidescreen;

	// Initialisation du système vidéo de SDL
	if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
        fprintf(stderr, "Impossible d'initialiser SDL: %s\n", SDL_GetError());
        exit(1);
    }
    
// SDL_SWSURFACE lent, SDL_HWACCEL lent, SDL_RLEACCEL lent, SDL_OPENGL no!, SDL_OPENGLBLIT no!, SDLOUBLEBUF lent, SDL_HWSURFACE lent
	screen = SDL_SetVideoMode(drawingarea->width(), drawingarea->height(), 24, SDL_SWSURFACE);
    if ( screen == NULL ) {
        fprintf(stderr, "Unable to set %ix%i video: %s\n", drawingarea->width(), drawingarea->height(), SDL_GetError());
    }

fprintf(stderr, "R: %x, %i, %i\n", screen->format->Rmask, screen->format->Rshift, screen->format->Rloss);
fprintf(stderr, "G: %x, %i, %i\n", screen->format->Gmask, screen->format->Gshift, screen->format->Gloss);
fprintf(stderr, "B: %x, %i, %i\n", screen->format->Bmask, screen->format->Bshift, screen->format->Bloss);
}

SdlDisplayer::~SdlDisplayer()
{
	cerr << ">> Destroying SDL Displayer" << endl;
}

DisplayerInput SdlDisplayer::format()
{
	return DISPLAY_BGR;
}

void SdlDisplayer::put( AVPicture *image )
{
//	AspectRatioCalculator calc( drawingarea->width(), drawingarea->height(),
//	                            this->preferredWidth(), this->preferredHeight(),
//	                            isPAL, isWidescreen );

	SDL_LockSurface(screen); // 1us

    // Nettoyage de l'écran
//    SDL_FillRect(screen, NULL, 0); // 400 us
    
    // Dessin
	memcpy( screen->pixels, image->data[0], screen->pitch * screen->clip_rect.h); // 800 us

    SDL_UnlockSurface(screen); // 3 us 
 
//struct timeval start;
//gettimeofday(&start, NULL);

    // Rafraîchissement...
    SDL_UpdateRect(screen, 0, 0, 0, 0); // 40 ms
//    SDL_UpdateRect(screen, drawingarea->x(), drawingarea->y(), drawingarea->width(), drawingarea->height()); // 40 ms

//struct timeval end;
//gettimeofday(&end, NULL);
//cerr<<(end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0<<endl; // 720x576 => 40ms
}


/** QGL Video Constructor. */
QGLDisplayer::QGLDisplayer( QWidget *drawingarea, int width, int height, bool isPAL, bool isWidescreen )
{
	cerr << ">> Trying QGL Video at " << width << "x" << height << endl;

	this->drawingarea = (GLVideo *)drawingarea;
	img_width = width;
	img_height = height;
	this->isPAL = isPAL;
	this->isWidescreen = isWidescreen;
}

QGLDisplayer::~QGLDisplayer()
{
	cerr << ">> Destroying QGL Displayer" << endl;
}

DisplayerInput QGLDisplayer::format()
{
	return DISPLAY_RGB;
}

void QGLDisplayer::put( AVPicture *image )
{
//	AspectRatioCalculator calc( drawingarea->width(), drawingarea->height(),
//	                            this->preferredWidth(), this->preferredHeight(),
//	                            isPAL, isWidescreen );
	drawingarea->setFrame(image);
}

Displayer *FindDisplayer::getDisplayer( QWidget *drawingarea, bool pal, bool width )
{
	return new QGLDisplayer( drawingarea, drawingarea->width(), drawingarea->height(), pal, width );
}
